package com.bestv.search.spider.youku.sync;

import com.bestv.search.spider.model.Person;
import com.bestv.search.spider.model.Product;
import com.bestv.search.spider.service.PersonManager;
import com.bestv.search.spider.util.PropertyStrategyWrapper;
import com.bestv.search.spider.util.SpiderConst;
import com.bestv.search.spider.util.SpiderUtil;
import com.bestv.search.spider.util.StringUtil;
import com.bestv.search.spider.youku.model.YouKuPersonJSON;
import com.bestv.search.spider.youku.util.YouKuAppKeyPool;
import com.bestv.search.spider.youku.util.YouKuUtil;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import net.sf.json.JsonConfig;
import net.sf.json.util.PropertySetStrategy;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.Hibernate;
import org.htmlparser.Node;
import org.htmlparser.NodeFilter;
import org.htmlparser.Parser;
import org.htmlparser.Tag;
import org.htmlparser.filters.TagNameFilter;
import org.htmlparser.util.NodeList;
import org.htmlparser.util.ParserException;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;

import java.io.File;
import java.net.URLEncoder;
import java.sql.Blob;
import java.sql.SQLException;
import java.text.SimpleDateFormat;
import java.util.*;

public class YouKuPersonSyncer extends QuartzJobBean {
    private static Log log = LogFactory.getLog(YouKuPersonSyncer.class);
    private static boolean isRunning = false;
    private PersonManager personManager;
    private String infoFolder;
    private int yearLimit;

    public static String convertToJson(Person person, PersonManager personManager) {
        JSONObject json = new JSONObject();
        json.put(SpiderConst.ID, person.getId());
        json.put(SpiderConst.PERSON_ID, person.getPersonId());
        json.put(SpiderConst.NAME, person.getName());
        // Gets avatar
        byte[] avatar = null;
        try {
            Blob blob = person.getAvatar_youku();
            if (blob != null) {
                avatar = SpiderUtil.convertStreamToByte(blob.getBinaryStream());
            }
        } catch (SQLException e) {
            log.warn("Get avatar failed for person " + person.getPersonId());
            log.warn(e);
        }
        json.put(SpiderConst.AVATAR_YOUKU, avatar == null ? SpiderConst.EMPTY : Arrays.toString(avatar));
        // Gets large avatar
        byte[] largeAvatar = null;
        try {
            Blob blob = person.getAvatar_iqiyi();
            if (blob != null) {
                largeAvatar = SpiderUtil.convertStreamToByte(blob.getBinaryStream());
            }
        } catch (SQLException e) {
            log.warn("Get large avatar failed for person " + person.getPersonId());
            log.warn(e);
        }
        json.put(SpiderConst.AVATAR_IQIYI, largeAvatar == null ? SpiderConst.EMPTY : Arrays.toString(largeAvatar));
        json.put(SpiderConst.GENDER, person.getGender());
        json.put(SpiderConst.NATIONALITY, person.getNationality());
        json.put(SpiderConst.BLOODTYPE, person.getBloodtype());
        json.put(SpiderConst.HEIGHT, person.getHeight());
        json.put(SpiderConst.OCCUPATION, person.getOccupation());
        json.put(SpiderConst.CONSTELLATION, person.getConstellation());
        json.put(SpiderConst.BIRTHDAY, person.getBirthday());
        json.put(SpiderConst.ALIAS, person.getAlias());
        json.put(SpiderConst.HOMEPLACE, person.getHomeplace());
        json.put(SpiderConst.DESCRIPTION, person.getDescription());
        json.put(SpiderConst.HONOR, person.getHonor());
        json.put(SpiderConst.SOURCE, person.getSource());

        List<Product> list = personManager.getProductsByPersonId(person.getPersonId());
        JSONArray products = new JSONArray();
        if (list != null && list.size() > 0) {
            for (Product product : list) {
                JSONObject jsonProduct = new JSONObject();
                jsonProduct.put(SpiderConst.PERSON_ID, product.getPersonId());
                jsonProduct.put(SpiderConst.NAME, product.getName());
                jsonProduct.put(SpiderConst.CATEGORY, product.getCategory());
                jsonProduct.put(SpiderConst.YEAR, product.getYear());
                jsonProduct.put(SpiderConst.DESCRIPTION, product.getDescription());
                products.add(jsonProduct);
            }
        }
        json.put(SpiderConst.PRODUCTS, products.toString());

        return json.toString();
    }

    @Override
    protected void executeInternal(JobExecutionContext arg) throws JobExecutionException {
        if (!isRunning) {
            isRunning = true;
            log.info("Start sync all persons from YouKu @ " + new Date());
            // Set running flag
            Calendar c = Calendar.getInstance();
            String fileFolderPath = getFileFolderByDateTime(c.getTime());

            // remove over 7 days' files.
            String parentFolder = fileFolderPath + File.separator + SpiderConst.POINT + SpiderConst.POINT
                    + File.separator;
            String[] overdueFolders = SpiderUtil.getOver7DaysFolders(parentFolder);
            if (overdueFolders != null) {
                for (String path : overdueFolders) {
                    SpiderUtil.deleteDirectory(parentFolder + path);
                }
            }

            String isRunningFile = fileFolderPath + File.separator + SpiderConst.ISRUNNING_FLAG_FILE;
            SpiderUtil.createFile(isRunningFile);

            boolean syncFinished = false;
            do {
                if (!YouKuAppKeyPool.isBusy) {
                    syncFinished = syncByUpdateTime(c.getTime());
                } else {
                    try {
                        Thread.sleep(SpiderConst.THREAD_CHECK_INTERVAL_SECOND * 1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            } while (!syncFinished);

            // Writes videos info to file as JSON format
            String file = fileFolderPath + File.separator + SpiderConst.ALL_PERSONS_FILE;
            List<Person> list = personManager.getAll();
            for (Person person : list) {
                String json = convertToJson(person, personManager);
                SpiderUtil.addTextToFile(json, file);
            }
            SpiderUtil.deleteFile(isRunningFile);

            isRunning = false;
            log.info("Sync all persons from YouKu end @ " + new Date());
        } else {
            log.info("Last persons sync process is still running");
        }
    }

    /**
     * @param date sync date time.
     * @return whether sync finished.
     */
    private boolean syncByUpdateTime(Date date) {
        List<Person> list = personManager.getByUpdateTime(date);

        if (list == null || list.size() == 0) {
            // Persons has sync end.
            return true;
        } else {
            List<Person> dbPersons = new ArrayList<Person>();
            for (Person person : list) {
                if (!YouKuAppKeyPool.isBusy) {
                    String url = StringUtil.buildBuffer(
                            new String[]{SpiderConst.YOUKU_PERSON_URL, SpiderConst.YOUKU_PERSON_ID,
                                    SpiderConst.EQUAL_FLAG, person.getPersonId()}).toString();
                    String response = new YouKuUtil().sendRequestByAppKey(url);
                    if (response != null && !response.equalsIgnoreCase(SpiderConst.EMPTY)) {
                        String nativeStr = StringUtil.ascii2Native(response);

                        // Convert to object
                        JSONObject json = JSONObject.fromObject(nativeStr);
                        JsonConfig cfg = new JsonConfig();
                        cfg.setPropertySetStrategy(new PropertyStrategyWrapper(PropertySetStrategy.DEFAULT));
                        cfg.setRootClass(YouKuPersonJSON.class);
                        YouKuPersonJSON personJSON = (YouKuPersonJSON) JSONObject.toBean(json, cfg);

                        // File JSON person to DB person.
                        fillDBPerson(person, personJSON, date);

                        // Gets user products
                        updateDBPerson(person, personJSON.getLink());
                    } else {
                        person.setUpdateTime(date);
                    }

                    dbPersons.add(person);
                    // save to DB
                    if (dbPersons.size() >= 100) {
                        personManager.updateList(dbPersons);
                        dbPersons = new ArrayList<Person>();
                    }
                } else {
                    log.info("AppKey pool is busy, please wait");
                    break;
                }
            }

            if (dbPersons != null && dbPersons.size() > 0) {
                personManager.updateList(dbPersons);
            }
            return false;
        }
    }

    private void fillDBPerson(Person person, YouKuPersonJSON personJSON, Date syncTime) {
        boolean syncImage = false;
        person.setName(personJSON.getName());

        if (personJSON.getAvatar_large() != null && person.getAvatar_youku() == null) {
            try {
                syncImage = true;
                String imageUrl = personJSON.getAvatar_large();
                byte[] data = SpiderUtil.getBytesByURL(imageUrl);
                person.setAvatar_youku(Hibernate.createBlob(data));
            } catch (Exception e) {
                log.warn("Get large avatar failed for person " + person.getPersonId());
                log.warn(e);
            }
        }

        person.setGender(personJSON.getGender());
        person.setNationality(personJSON.getNationality());
        person.setBloodtype(personJSON.getBloodtype());
        person.setHeight(personJSON.getHeight());
        person.setOccupation(Arrays.toString(personJSON.getOccupation()));
        person.setConstellation(personJSON.getConstellation());
        person.setBirthday(personJSON.getBirthday());
        person.setHomeplace(personJSON.getHomeplace());
        person.setAlias(Arrays.toString(personJSON.getAlias()));
        person.setDescription(personJSON.getDescription());
        person.setUpdateTime(syncTime);
        person.setSource(SpiderConst.YOUKU);

        if (syncImage) {
            // Sleep 3 seconds
            try {
                Thread.sleep(1000 * 3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    private void updateDBPerson(Person person, String personLink) {
        SpiderUtil spider = new SpiderUtil();

        // Gets honor
        String link = StringUtil.buildBuffer(
                new String[]{SpiderConst.YOUKU_PERSON_PRIZE_URL, String.valueOf(person.getPersonId())}).toString();
        String response = spider.sendRequest(link);
        if (response != null && !response.equals(SpiderConst.EMPTY)) {
            Parser p = new Parser();
            try {
                p.setInputHTML(response);
                // gets honor of person
                NodeFilter liFilter = new TagNameFilter(SpiderConst.LI);
                NodeList liNodes = p.parse(liFilter);
                StringBuilder honor = new StringBuilder();
                for (int i = 0; i < liNodes.size(); i++) {
                    Node li = liNodes.elementAt(i);

                    NodeList childrens = li.getChildren();
                    for (int j = 0; j < childrens.size(); j++) {
                        Node element = childrens.elementAt(j);
                        if (element instanceof Tag) {
                            honor.append(childrens.elementAt(j).toPlainTextString());
                            if (j != childrens.size() - 1) {
                                honor.append(SpiderConst.SPACE);
                            }
                        }
                    }
                    if (i != liNodes.size() - 1) {
                        honor.append(SpiderConst.COMMA);
                    }
                }
                person.setHonor(honor.toString());
            } catch (ParserException e) {
                log.error("Get short avatar failed from iqiyi ");
                log.error(e);
                e.printStackTrace();
            }
        }

        // gets products of person
        try {
            spider.sendRequest(personLink);
            boolean isFinished = false;
            int startIndex = 1;
            Calendar now = Calendar.getInstance();
            int lastYear = now.get(Calendar.YEAR) + 1;
            Set<Product> products = new HashSet<Product>();
            do {
                if (now.get(Calendar.YEAR) + 1 - lastYear > this.getYearLimit()) {
                    break;
                }
                @SuppressWarnings("deprecation")
                String url = StringUtil.buildBuffer(
                        new String[]{SpiderConst.SOKU_PERSON_AJAX_URL, SpiderConst.URL_SPLIT_FLAG,
                                SpiderConst.PERSON, SpiderConst.EQUAL_FLAG, String.valueOf(person.getPersonId()),
                                SpiderConst.URL_SPLIT_FLAG, SpiderConst.PERSON_NAME, SpiderConst.EQUAL_FLAG,
                                URLEncoder.encode(person.getName()), SpiderConst.URL_SPLIT_FLAG, SpiderConst.LASTYEAR,
                                SpiderConst.EQUAL_FLAG, String.valueOf(lastYear), SpiderConst.URL_SPLIT_FLAG,
                                SpiderConst.STARTINDRX, SpiderConst.EQUAL_FLAG, String.valueOf(startIndex)})
                        .toString();

                String productResponse = spider.sendRequest(url);
                if (productResponse != null && !productResponse.equals(SpiderConst.EMPTY)) {
                    Parser htmlParser = new Parser();
                    htmlParser.setInputHTML(productResponse);
                    // gets products of person
                    NodeFilter trFilter = new TagNameFilter(SpiderConst.TR);
                    NodeList trNodes = htmlParser.parse(trFilter);
                    if (trNodes != null && trNodes.size() > 0) {
                        for (int i = 0; i < trNodes.size(); i++) {
                            Tag tr = (Tag) trNodes.elementAt(i);
                            String endIndex = tr.getAttribute(SpiderConst.ENDINDRX);
                            if (endIndex != null && !endIndex.equals(SpiderConst.EMPTY)) {
                                startIndex = Integer.valueOf(endIndex);
                                int trYear = Integer.valueOf(tr.getAttribute(SpiderConst.LASTYEAR));
                                lastYear = Math.min(trYear, lastYear);

                                Product product = new Product();
                                product.setPersonId(person.getPersonId());

                                // Gets products
                                NodeFilter tdFilter = new TagNameFilter(SpiderConst.TD);
                                NodeList tdTags = new NodeList();
                                tr.collectInto(tdTags, tdFilter);
                                for (int j = 0; j < tdTags.size(); j++) {
                                    product.setYear(trYear);
                                    Tag td = (Tag) tdTags.elementAt(j);
                                    String className = td.getAttribute(SpiderConst.CLASS);
                                    if (className != null && !className.equals(SpiderConst.EMPTY)) {
                                        if (className.equals(SpiderConst.TYPE)) {
                                            product.setCategory(td.toPlainTextString());
                                        } else if (className.equals(SpiderConst.TITLE)) {
                                            NodeList tdNames = td.getChildren();
                                            for (int m = 0; m < tdNames.size(); m++) {
                                                Node tagNode = tdNames.elementAt(m);
                                                if (tagNode instanceof Tag) {
                                                    product.setName(tagNode.toPlainTextString());
                                                }
                                            }
                                        } else if (className.equals(SpiderConst.ROLE)) {
                                            product.setDescription(td.toPlainTextString().replaceAll(SpiderConst.TAB,
                                                    SpiderConst.EMPTY));
                                        }
                                    }
                                }

                                String id = product.getPersonId() + Math.abs(product.getName().hashCode())
                                        + Math.abs(product.getCategory().hashCode());
                                product.setId(id);
                                product.setUpdateTime(new Date());

                                products.add(product);
                            }
                        }
                    } else {
                        isFinished = true;
                    }
                } else {
                    isFinished = true;
                }
            } while (!isFinished);

            person.setProducts(products);
        } catch (ParserException e) {
            log.error("Get short avatar failed from iqiyi ");
            log.error(e);
            e.printStackTrace();
        }
    }

    private String getFileFolderByDateTime(Date date) {
        String personFolder = this.getInfoFolder() + File.separator + SpiderConst.PERSON;
        File folder = new File(personFolder);
        if (!folder.exists()) {
            folder.mkdir();
        }

        personFolder = personFolder + File.separator + SpiderConst.YOUKU;
        folder = new File(personFolder);
        if (!folder.exists()) {
            folder.mkdir();
        }

        SimpleDateFormat format = new SimpleDateFormat(SpiderConst.DATETIME_FORMAT);
        String dateTimeFolder = personFolder + File.separator + format.format(date);
        folder = new File(dateTimeFolder);
        if (!folder.exists()) {
            folder.mkdir();
        }
        return dateTimeFolder;
    }

    public String getInfoFolder() {
        return infoFolder;
    }

    public void setInfoFolder(String infoFolder) {
        this.infoFolder = infoFolder;
    }

    public PersonManager getPersonManager() {
        return personManager;
    }

    public void setPersonManager(PersonManager personManager) {
        this.personManager = personManager;
    }

    public int getYearLimit() {
        return yearLimit;
    }

    public void setYearLimit(int yearLimit) {
        this.yearLimit = yearLimit;
    }
}
